Skip to content

Feat: Generate TypeScript API client from OpenAPI spec#180

Merged
kilodesodiq-arch merged 3 commits into
ChainForgee:mainfrom
Lansa-18:feat/issue-118-openapi-ts-client
Jun 22, 2026
Merged

Feat: Generate TypeScript API client from OpenAPI spec#180
kilodesodiq-arch merged 3 commits into
ChainForgee:mainfrom
Lansa-18:feat/issue-118-openapi-ts-client

Conversation

@Lansa-18

Copy link
Copy Markdown
Contributor

Closes #118

Feat: Generate Typed TypeScript HTTP Client from OpenAPI Spec #118

Summary

Generates a fully typed TypeScript HTTP client from the NestJS Swagger/OpenAPI spec and wires it into the frontend, replacing raw fetch calls with compile-time–safe URL and request-body checking.


Backend

  • Extracts DocumentBuilder config into src/swagger.config.ts
  • Adds scripts/export-spec.ts — bootstraps NestJS without an HTTP listener and writes openapi.json to the frontend directory; registered as npm run spec:export
  • Fixes missing @ApiBody decorators on verification-inbox.controller.ts (approve, reject, requestResubmission, addInternalNote) — bodies were accepted at runtime but invisible in the spec
  • Adds additionalProperties: true to the metadata field in CreateCampaignDto / UpdateCampaignDto so the generated types produce Record<string, unknown> instead of Record<string, never>

Frontend

  • Adds openapi-fetch (runtime client) and openapi-typescript (codegen) as dependencies; pnpm generate:api regenerates src/lib/generated/api.ts from openapi.json
  • Commits openapi.json and src/lib/generated/api.ts (do not edit by hand)
  • Adds src/lib/api-client.tscreateClient<paths> routed through the existing fetchClient mock layer so NEXT_PUBLIC_USE_MOCKS=true continues to work transparently
  • Migrates useCampaigns, useHealthStatus, useOptimisticCampaignMutations, verification-api, verification-inbox-api, and AidDistributionMap to the typed client
  • Fixes pre-existing URL prefix bugs uncovered during migration: hooks were calling /campaigns, /health, /verification-inbox etc. without the required /api/v1/ prefix
  • useAidPackages and csv-validation (/recipients/import/*) intentionally kept on raw fetchClient — these endpoints are mock-only and absent from the backend Swagger spec

CI

Adds two steps after Build in backend-ci.yml: re-runs spec:export, then git diff --exit-code app/frontend/openapi.json to fail the pipeline if the committed spec has drifted from the backend.


Maintainer Notice — Pre-existing Frontend Routing Issue (out of scope for this PR)

While smoke-testing the frontend during this PR, a pre-existing issue was found that prevents the frontend from rendering at all on main.

The root layout (src/app/layout.tsx) calls getMessages() from next-intl/server outside the [locale] route segment. Without a locale middleware to inject the locale into the request context, getRequestConfig receives requestLocale = undefined, hits the notFound() guard in src/i18n.ts, and every page returns 404. Additionally, Next.js 16 (used by this project) has renamed middleware.ts to proxy.ts, so the old convention no longer applies.

The fix is: (1) add proxy.ts at app/frontend/proxy.ts using next-intl/middleware's createMiddleware for locale detection and redirect, and (2) move getMessages() and NextIntlClientProvider from the root layout into src/app/[locale]/layout.tsx where locale context is available.


Testing

# Backend — export spec, unit tests, lint
cd app/backend
DATABASE_URL="file:./prisma/dev.db" npm run spec:export   # openapi.json written
npm test                                                   # all unit tests pass
npm run lint:check                                         # no errors in changed files

# Frontend — regenerate types, type-check, tests, lint
cd app/frontend
pnpm generate:api      # src/lib/generated/api.ts regenerated in 42ms
pnpm tsc --noEmit      # 0 type errors
pnpm test              # 82 tests pass across 9 suites

Manual verification:

  • Backend started (npm run start:dev) and Swagger UI confirmed live at http://localhost:3000/api/docs — all tag groups visible (Analytics, Campaigns, Verification Inbox, etc.)
  • Mock API tests (mock-api/client.test.ts) confirm the fetchClient interception layer still works through apiClient when NEXT_PUBLIC_USE_MOCKS=true

Checklist

  • Tests added or updated for new/changed behavior
  • Existing tests pass
  • No secrets, keys, or seed phrases committed
  • Follows the coding conventions in the relevant service README
  • PR is focused on a single concern

Lansa-18 added 3 commits June 21, 2026 14:35
The secret is only needed when sign()/verify() are actually called, not
during module initialization. This allows the spec export script (and
any other non-webhook boot path) to start the app without WEBHOOK_SECRET
present. If called without a configured secret, it still fails loudly.
@Lansa-18

Copy link
Copy Markdown
Contributor Author

@kilodesodiq-arch Kindly review.

Copy link
Copy Markdown
Contributor

Thanks @Lansa-18 — solid generated TS client with proper types. Backend CI is green and this resolves #118 cleanly. Merging now.

@kilodesodiq-arch kilodesodiq-arch merged commit fbc4805 into ChainForgee:main Jun 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ENHANCEMENT] Generate TypeScript API client from OpenAPI/Swagger spec for frontend

2 participants